-
-
Notifications
You must be signed in to change notification settings - Fork 33.3k
http: add optimizeEmptyRequests option for IncomingMessage._dump #59778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
7961667
to
6e5fdcb
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #59778 +/- ##
==========================================
- Coverage 88.45% 88.43% -0.03%
==========================================
Files 703 703
Lines 207544 207577 +33
Branches 40013 40016 +3
==========================================
- Hits 183587 183574 -13
- Misses 15965 15996 +31
- Partials 7992 8007 +15
🚀 New features to boost your workflow:
|
lib/_http_server.js
Outdated
const fastDump = options.fastDump; | ||
if (fastDump !== undefined) | ||
validateBoolean(fastDump, 'options.fastDump'); | ||
this[kfastDump] = fastDump || false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OR seems redudant since we are passing through validateBoolean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How so? if fastDump is undefined, this[kfastDump]
will be undefined instead of false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could shortcut that just a bit by assigning a default:
const { fastDump = false } = options.fastDump;
validateBoolean(fastDump, 'options.fastDump');
this[kfastDump] = fastDump;
Thinking more about the heuristics mentioned at the end #59747, it looks like we can go further and do even better here, very easily. I think we can do this for all methods, quickly & safely, because it's already impossible to receive a request with a body without it being indicated it in the headers (content-length or transfer-encoding), even in the current implementation, even for POST and others etc. On Node v24 right now, if you send:
Then Node currently fires That means we could safely apply this optimization to all cases, whenever there's no This would still be breaking behaviour, because it doesn't emit end or close events, but as an opt-in option ( |
6e5fdcb
to
2d617ab
Compare
Just pushed a new version (amended) that replaces the PTAL. |
lib/_http_server.js
Outdated
// stream.Readable life cycle rules. The downside is that this will | ||
// break some servers that read bodies for methods that don't have body headers. | ||
req._dumped = true; | ||
req._readableState.ended = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we move the modifications of stream internals into stream files?
I wonder also that endEmitted
/ closeEmitted
are set here. Are they really emitted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder also that endEmitted/ closeEmitted are set here. Are they really emitted?
They are as if they were already emitted.
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately this new option is not easy to turn on by default because it's blocking a valid case, the HTTP/1.0 case where a request is made and the socket half closed to signal its termination. I would rather have it enabled based on the HTTP method instead.
@mcollina I don't think that case exists (or has ever existed). You might be thinking of the response case, where you can use connection closure to delimit bodies without using any framing headers? That's not possible for requests though, and response behaviour doesn't affect this PR. For requests, the HTTP/1.0 RFC says:
and later:
I'm not even sure it would be technically possible - if you could just send content + TCP half-close to send a request body, there would be know way to ever know if a request was finished without the half-close. On any still-open connection, there could always be more request body to come. I'd be very interested to see a counter-example, but I've just tested with a bunch of raw requets via socat and I can't see any way to make Node currently accept a request body like this, so I think we can ignore it. Same applies for HTTP/0.9.
Totally agree we can't turn this on by default anyway though, since it's a significant breaking change that will affect a lot of web frameworks & middleware etc and cause very confusing issues. It would be great to find a way to enable this everywhere, but doing that without breakage would require emulating the actual stream behaviour (at the least, firing readable & end & close events, and probably simulating the initial paused-readable behaviour as well) and I suspect that would negate a lot of the benefit. We could explore this separately in future later if it seems worthwhile, it would definitely be good to do if we can find a way. In the meantime, releasing the current approach allows the ecosystem to start adding the readableEnded logic to support this optimization safely now, with no downside, and if that happens widely enough we might be able to enable as-is in the distant future. |
All LGTM now (thanks @RafaelGSS!). Just one last thing worth noting here: when this optimization is applied, it means you also can't listen to I think that's fine (you can listen for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
on-finished would technically break with these changes, since it's not detecting when IncomingMessage has completed (https://github.com/jshttp/on-finished/blob/d2974f5a18f468ea56f58acb2f6d402f4b5142f0/index.js#L92). Do you know of any patch to fix it from on-finished and also be able to complete expressjs/express#6742? |
} | ||
res.writeHead(200); | ||
res.end('ok'); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it's not clear in this test what "be optimized" means. Expanding on some code comments could help provide context to anyone coming into this code later.
a2b6157
to
e33471d
Compare
This comment was marked as outdated.
This comment was marked as outdated.
e33471d
to
e6cd7d2
Compare
doc/api/http.md
Outdated
or `Transfer-Encoding` headers (indicating no body) will be initialized with an | ||
already-ended body stream, so they will never emit any stream events | ||
(like `'data'` or `'end'`). You can use `req.readableEnded` to detect this case. | ||
This option is still under experimental phase. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is indeed true, we should emit an experimental warning whenever this flag is used. If this is no longer correct, we should remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Than I recommend emitting a warning if it's not inside node_modules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added the experimental documentation here only for the next two v24 releases at least, just in case we are missing some specific edge case that would completely change how this flag works - you know, touching on http usually breaks people in many ways. Ideally, this line should be removed in a couple of releases.
I'm not sure if it's worth it to consider such a warning since we'll remove it very soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@anonrig I have removed the experimental comment.
Signed-off-by: RafaelGSS <[email protected]> Co-Authored-By: RafaelGSS <[email protected]>
This removes the potential TOCTOU condition that will make this test flaky Signed-off-by: RafaelGSS <[email protected]>
e6cd7d2
to
29753ac
Compare
Ping @anonrig |
Backportable version of #59747
cc: @ronag